#ifndef _PSO_H_
#define _PSO_H_

#include <stdlib.h>
#include "Viewer.h"
#include "GaussianMixtureChart.h"
#include "FunctionAndPointsChart.h"

class ProbabilisticSearch
{
public:

	ProbabilisticSearch(double (*fitness_function)(double *particle, void *data), double **limits, int dim, void *data = NULL, int num_particles = 70, int max_iteractions = 100);
	~ProbabilisticSearch();
	
	void Optimize();
	double GetBestFitness();
	double *GetBestSolution();
	double *GetErrorEvolution();

protected:

	void _alloc_stuff();
	void _dealloc_stuff();
	void _randomize_particles();
	void _intialize_best_particle();
	void _evaluate_fitness(double **particles, double *fitness);
	void _update_best_particle();
	void _update_particles_with_selective_gaussian_mixture();
	void _copy(double from[], double to[]);
	void _compute_particle_probability_from_fitness(double *fitness, double *probabilities);
	void _build_selective_mixture_of_gaussians(double *probabilities);
	void _sample_new_particles_from_distribution();
	void _replace_particles_with_better_fitness();
	double _get_min_fitness();
	int _get_particle_with_min_fitness();
	void _calculate_stds_from_probabilities();
	void _select_particles_to_compose_distribution();
	void _view_selectede_particles_to_compose_distribution();
	int _find_distribution_with_maximum_probability(double *particle);
	void _set_distribution_as_used(int distribution);
	double _compute_probability_of_particle_using_distribution(double *particle, int distribution_id);
	void _calculate_probability_of_selected_distributions();
	int _sample_gaussian_from_mixture();
	void _sample_particle_from_gaussian(int distribution_id, double *particle);

	int _max_iteractions; 
	int _num_particles;
	int _dim;

	double **_particles;					// [num_particles][dim];
	double **_limits;						// [dim][2];

	double *_fitness;						// [num_particles];
	double *_best_particle;					// [dim];
	double *_error_evolution;				// [num_iteractions]
	double *_particle_probability;			// [num_particles];
	double *_particle_std;				// [num_particles];
	double _best_fitness;

	double (*_fitness_function)(double *particle, void *data);
	void *_data;

	// if USE_GAUSSIAN_DISTRO is set to 1, then the resampling
	// method uses the selective mixture of gaussians.
	// If USE_GAUSSIAN_DISTRO is set to 0, then a random
	// distribution is used to resample the particles
	static const int USE_GAUSSIAN_DISTRO = 1;
	// if REPLACE_THE_WORST is set to 1, then classical elitist selection is
	// used. In this situation, if the new particle's fitness is better than
	// the worst old particle's fitness, the new particle replaces the old.
	// As it have to find the worst particle, its slowest than the other method.
	// if REPLACE_THE_WORST is set to 0, then a approximate elitist selection is
	// used. In this case, if the i-th particle have a better fitness than the i-th
	// old particle's fitness, the new particle replaces the old. In other words,
	//  an old particle is replaced if the equivalent new particle has improved.
	// It's fatest because it doesn't have to find the worst particle. However, it
	// doesn't guarantee that all best particles from new's set and old's set are maintained.
	static const int REPLACE_THE_WORST = 0;

	// Note: These variables are used to describe the selective
	// gaussian mixture distribution. The means are the particles'
	// values and the stds are calculated using the probability of
	// each particle. The probability is calculated using the particle
	// fitness. The indices of the particles selected to compose the
	// selective gaussian are stored in the _used_particles variable.
	double *_gaussian_probability;			// [num_particles];
	double *_gaussian_cumulative_probability; // [num_particles];
	int *_selected_gaussians;				// [num_particles];
	int _num_selected_gaussians;

	// Note: these variables are used during some calculations. I created
	// them here to avoid reallocations in different function calls.
	double *_fitness_of_new_particles;		// [num_particles];
	double **_new_particles;				// [num_particles][dim];

	// debug viewer
	Viewer _viewer;
};

#endif 
